Skip to content

[4.x] Add token-based security for cart loading#4207

Merged
lukeholder merged 13 commits into
4.xfrom
feature/load-cart-token-4x
Jun 3, 2026
Merged

[4.x] Add token-based security for cart loading#4207
lukeholder merged 13 commits into
4.xfrom
feature/load-cart-token-4x

Conversation

@lukeholder

@lukeholder lukeholder commented Jan 21, 2026

Copy link
Copy Markdown
Member

Summary

Adds token-based security to the cart loading action, with a cart recovery email flow for users whose token has expired or is missing.

Changes

Security

  • Cart load URLs are now generated using Craft's built-in token system, creating time-limited, cryptographically-bound one-use URLs.
  • commerce/cart/load-cart now validates a code query parameter. Without a valid token, the user must be logged in as the cart's owner to proceed.
  • Carts with no email address and no billing/shipping addresses bypass token validation (anonymous/empty carts load freely, as before).

Cart Recovery Flow

  • Users with an invalid or missing token are redirected to an email challenge form (commerce/cart/email-challenge).
  • Submitting the form sends a fresh, time-limited recovery link to the cart's email address.
  • Added a new commerce_cart_recovery system message (customizable subject/body via Settings > Emails).
  • New CP-rendered templates: _cart/email-challenge.twig and _cart/email-sent.twig.
  • commerce/cart/load-cart returns JSON with a challengeUrl key for Accept: application/json requests on failure.

Settings

  • Added cartLoadUrlExpiry setting (int, seconds; default: 604800 / 7 days) to control how long cart load links remain valid.

CP "Share Cart" Element Action

  • The action now fetches a fresh tokenized URL via an AJAX request to commerce/orders/get-load-cart-url rather than constructing a static URL client-side, so every copied URL has a valid token.

New / Changed APIs

  • craft\commerce\services\Carts::getLoadCartUrl(Order $cart): string — creates a Craft token and returns the full load-cart URL.
  • craft\commerce\elements\Order::getLoadCartUrl() — now delegates to Carts::getLoadCartUrl() and returns a tokenized URL.
  • craft\commerce\controllers\CartController::actionEmailChallenge() — renders the cart recovery email challenge form.
  • craft\commerce\controllers\CartController::actionCartChallenge() — handles form submission and sends the recovery email.
  • craft\commerce\controllers\CartController::actionCartSent() — renders the post-send confirmation page.
  • craft\commerce\controllers\OrdersController::actionGetLoadCartUrl() — JSON endpoint (requires commerce-manageOrders) used by the CP "Share cart" element action.

- Add secure token validation to load-cart action
  - Carts with email/addresses require valid token or owner authentication
  - Carts without sensitive data can load without token
  - Add email challenge flow for unauthenticated cart recovery
  - Register commerce_cart_recovery system message for recovery emails
  - Add cartLinkExpiry setting (default 24 hours)
  - Add getLoadCartUrl() to Carts service for generating secure URLs
@lukeholder lukeholder changed the title Add token-based security for cart loading [4.x] Add token-based security for cart loading Jan 21, 2026
@rlarabee

rlarabee commented Jan 25, 2026

Copy link
Copy Markdown

Take a look at _getCart() in CartController.php as well because it is called by actionUpdateCart() and actionComplete() and any other cart modification actions, make sure the same validation is applied. I am not sure how this will affect the over all functionality, but from a cryptographic standpoint, for the generateCartNumber, I would move to something like bin2hex(random_bytes(16)).

@lukeholder lukeholder mentioned this pull request May 20, 2026
7 tasks
@lukeholder lukeholder marked this pull request as ready for review May 20, 2026 09:14
@lukeholder lukeholder requested a review from a team as a code owner May 20, 2026 09:14
@lukeholder lukeholder merged commit 9c9bbb4 into 4.x Jun 3, 2026
@lukeholder lukeholder deleted the feature/load-cart-token-4x branch June 3, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants